上一篇讲菜单项中的Action 与 HistoryFunction. 两者之间的区别, 暂时还不甚了然. 先放一放, 这一篇整理一下Paint.Net中的Effect
Effect是以Plugin的方式提供的, 可以使用单独的dll, 放在特定的目录下. 程序启动时, 会自动加载.但Paint.Net并不是插件结构. 和SharpDevelop不同, SharpDevelop中界面的UI也是以Plugin的方式提供的.
Effect滤镜的主要逻辑在EffectMenuBase.cs中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32private void EffectMenuItem_Click(object sender, EventArgs e)
{
if (AppWorkspace.ActiveDocumentWorkspace == null)
{
return;
}
PdnMenuItem pmi = (PdnMenuItem)sender;
Type effectType = (Type)pmi.Tag;
RunEffect(effectType);
}
public void RunEffect(Type effectType)
{
bool oldDirtyValue = AppWorkspace.ActiveDocumentWorkspace.Document.Dirty;
bool resetDirtyValue = false;
AppWorkspace.Update(); // make sure the window is done 'closing'
AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar();
DocumentWorkspace activeDW = AppWorkspace.ActiveDocumentWorkspace;
PdnRegion selectedRegion; //选择区域,滤镜是应用于整幅图像,还是一个选择区域。下图就是只应用于一个选择区域
if (activeDW.Selection.IsEmpty)
{
selectedRegion = new PdnRegion(activeDW.Document.Bounds);
}
else
{
selectedRegion = activeDW.Selection.CreateRegion();
}

这里Effect通过EffectFlags.Configurable分为2类(有没有参数配置,可以调整参数)
第一类(没有参数配置):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28if (!(effect.CheckForEffectFlags(EffectFlags.Configurable)))
{
Surface copy = activeDW.BorrowScratchSurface(this.GetType() + ".RunEffect() using scratch surface for non-configurable rendering");
try
{
using (new WaitCursorChanger(AppWorkspace))
{
copy.CopySurface(layer.Surface);
}
EffectEnvironmentParameters eep = new EffectEnvironmentParameters(
AppWorkspace.AppEnvironment.PrimaryColor,
AppWorkspace.AppEnvironment.SecondaryColor,
AppWorkspace.AppEnvironment.PenInfo.Width,
selectedRegion,
copy);
effect.EnvironmentParameters = eep;
DoEffect(effect, null, selectedRegion, selectedRegion, copy, out exception);
}
finally
{
activeDW.ReturnScratchSurface(copy);
}
}
第二类(有参数配置):有参数配置,就需要一个调整参数的对话框,例如上图中的高斯模糊. 先是创建一个EffectConfigDialog1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using (EffectConfigDialog configDialog = effect.CreateConfigDialog())
{
configDialog.Opacity = 0.9;
configDialog.Effect = effect;
configDialog.EffectSourceSurface = originalSurface;
configDialog.Selection = selectedRegion;
BackgroundEffectRenderer ber = null;
EventHandler eh =
delegate(object sender, EventArgs e)
{
EffectConfigDialog ecf = (EffectConfigDialog)sender;
if (ber != null)
{
AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBarAsync();
try
{
ber.Start();
}
catch (Exception ex)
{
exception = ex;
ecf.Close();
}
}
};
configDialog.EffectTokenChanged += eh;
}
如何创建EffectConfigDialog应该是Paint.Net中最有技巧性的东西了,真是大开眼界
翻看了不少代码,大致明白了是怎么回事。因为Effect在Paint.Net中是以plugin的形式存在,假如Effect有参数可以调整,那么在什么地方去调整参数呢?想像一下,我们需要有一个参数的Dialog。在没有源码的情况下,如何创建这个Dialog。以高斯模糊为例,只要该Effect提供参数,由Paint.Net帮你创建EffectConfigDialog
Paint.Net在框架中提供了一种参数与Control的一一对应,然后根据参数列表,创建一个一个的Control,然后用一个Panel将这些Control添加进来。再提供一个参数变更引发的事件。
这些参数用一个叫做EffectConfigToken的类封装起来(具体是PropertyCollection)
以高斯模糊为例1
2
3
4
5
6
7
8
9
10
11public sealed class GaussianBlurEffect
: InternalPropertyBasedEffect
public abstract class InternalPropertyBasedEffect
: PropertyBasedEffect
public abstract class PropertyBasedEffect
: Effect<PropertyBasedEffectConfigToken>
public sealed class PropertyBasedEffectConfigToken
: EffectConfigToken
其中1
2internal sealed class PropertyBasedEffectConfigDialog
: EffectConfigDialog<PropertyBasedEffect, PropertyBasedEffectConfigToken>
AmountEffectConfigDialog.cs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22private void amountUpDown_ValueChanged(object sender, System.EventArgs e)
{
if (amountTrackBar.Value != (int)amountUpDown.Value)
{
amountTrackBar.Value = (int)amountUpDown.Value;
FinishTokenUpdate();
}
}
public void FinishTokenUpdate()
{
InitTokenFromDialog();
OnEffectTokenChanged();
}
protected virtual void OnEffectTokenChanged()
{
if (EffectTokenChanged != null)
{
EffectTokenChanged(this, EventArgs.Empty);
}
}
扫描有多少个Effect,这部分是以插件的形式出现的,加载后出现在菜单项中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47private static EffectsCollection GatherEffects()
{
List<Assembly> assemblies = new List<Assembly>();
// PaintDotNet.Effects.dll
assemblies.Add(Assembly.GetAssembly(typeof(Effect)));
// TARGETDIR\Effects\*.dll
string homeDir = PdnInfo.GetApplicationDir();
string effectsDir = Path.Combine(homeDir, InvariantStrings.EffectsSubDir);
bool dirExists;
try
{
dirExists = Directory.Exists(effectsDir);
}
catch
{
dirExists = false;
}
if (dirExists)
{
string fileSpec = "*" + InvariantStrings.DllExtension;
string[] filePaths = Directory.GetFiles(effectsDir, fileSpec);
foreach (string filePath in filePaths)
{
Assembly pluginAssembly = null;
try
{
pluginAssembly = Assembly.LoadFrom(filePath);
assemblies.Add(pluginAssembly);
}
catch (Exception ex)
{
Tracing.Ping("Exception while loading " + filePath + ": " + ex.ToString());
}
}
}
EffectsCollection ec = new EffectsCollection(assemblies);
return ec;
}